Pause duration, a daily reminder, cool.jacoblin.particeps, and a documentation pass - #29
Merged
Merged
Conversation
A pause is the one state a participant can leave a study in by accident. Nothing on the phone changes, no notification is showing, and a study meant to run for a fortnight quietly records nothing until someone thinks to open the app. The running case is not padding either: a study that collects for weeks should keep saying so, because consent nobody is reminded of is consent in name only. One notification a day, replacing yesterday's rather than stacking, on its own low-importance channel. Daily is long enough that anything noisier would be a reason to uninstall, which would end a study far more effectively than a missed reminder would. It reports state and nothing else — no counts, no collector names, no study content — because a notification is readable on a lock screen by whoever is holding the phone. The paused text names when the pause started, since a reminder that only said "Paused" leaves no way to tell five minutes from a fortnight. Periodic rather than the self-renewing chain used for delivery: a day is far above WorkManager's fifteen-minute floor, so nothing is silently clamped, and the platform re-establishes periodic work across reboots. Scheduled when collection starts and re-established on session init, so a study already under way before this existed also gets it; deliberately not cancelled on pause, and retired with the rest of the collection work when the study ends.
The previous namespace carried a personal handle that the product does not use anywhere else, and the release certificate's subject still named the pre-rename product. Neither can be corrected later: an applicationId is how Android identifies an installed application, and a certificate is signed over its own subject, so changing either issues something a device treats as a different app. Doing both now costs nothing, because every tag published so far is a pre-1.0 release candidate and Developer Verification has not been registered against any of them. After a released build reaches a participant it would cost a great deal, so this is the last time. The audit that pins the fresh-install boundary now knows about two dead namespaces rather than one, and refuses a reference to either. The release guide, the README, and invariant 8 no longer claim the signing key was deliberately preserved, which was true of the first rename and is not true now.
|
Pre-merge review found two defects in the reminder, both mine. The notification put the researcher-supplied study title in its content title, while the same file's comment claimed it disclosed no study content. A study called "Smartphone use and depressive symptoms in adolescents" would have rendered as the bold first line of a lock-screen notification, every day, for the study's whole duration — including while paused, a state in which the phone previously showed nothing at all. The title is now the app's own name, which the launcher and Android's settings already display. The ongoing collection notification still carries the study title, but only while collection is really running and only as its secondary line. Pausing also left the already-posted notification standing, so the lock screen could keep asserting "Still collecting" for up to a day after collection stopped — the exact opposite of what the reminder is for. Starting or stopping collection now retracts it; the next daily run posts the truth. Re-posting immediately would turn a daily reminder into a notification on every pause. The audit gained a repository-wide pattern for cool.linc.particeps. Unlike the first retired namespace it contains no ADC spelling, so it had been enforced only on the six suffixes the boundary check reads, and a regression in a Python tool, a workflow, or TypeScript would have passed. Verified by injection into all three. The participant guide now describes the reminder. It enumerates every notification a participant sees, and this one was missing from that list and from the notification-permission section.
It was a plan, written in the future tense about work that shipped three releases ago, and it had become a de facto changelog nobody updates. Every section restated a document that owns the subject: the invariants compress protocol/v1/README.md and system-design.md, the responsibility map repeats system-design's module table, the upload transaction was a third copy of the receipt contract, and the P2 design limits are covered in more detail by the researcher guide. One section was not a duplicate. The five phase-review criteria are a standard for finishing work, so they move to CONTRIBUTING.md, where the person about to open a pull request will see them. CONTRIBUTING.md also claimed the collector capability boundary is enforced by review rather than by a check. It is enforced by tools/collector_assurance.py on every pull request. The claim is replaced by what the check actually covers, and by the gap it does not — the manifest, which issue #11 tracks.
…sing An audit compared every document to the tree it describes. This fixes what it found wrong, and adds three things that were true but written down nowhere. It changes nothing for style; duplication and sentence length are a later pass. The most important gap was in the participant guide. It documented one way to enrol — the system file picker — while the application also ships a particeps://join/v1 handler and the researcher guide tells researchers to recruit with a link or a QR code. The participant scanning that code held the one document that never mentioned it, and none of the four codes that path can produce appeared in its troubleshooting section. One of those four is not a fault: a join is refused while another study or a pending deletion exists. That path also carries a limitation nobody had written down. The join URI carries signer_fingerprint as a query parameter, so a participant who arrives by link is shown a fingerprint that came from that same link. Comparing the two shows only that the link agrees with itself. The guide now says so where it explains the check. The daily status reminder existed in code and in one sentence of the participant guide. It is now in the threat model as a disclosure surface a bystander can read without decrypting anything, in the system design's background-execution section, and in the researcher guide where a researcher plans participant contact — with its limits: best-effort timing, no configuration field to switch it off, and no guarantee anyone was reminded. The collector implementation guide was wrong in six places that would each have misled someone writing a collector: EmitResult has four members and the guide documented three, AccessKind has ten and the guide listed seven, two lifecycle signatures were quoted with the wrong return types, the module was said to hold two files and holds five, a guard was named that does not exist, and a label hook was named that does not exist. Verified against the source, member by member. Smaller corrections: the release guide presented a finished cutover as pending work; the system design over-claimed what the participant dashboard shows per collector; the data dictionary promised every exported field and omitted the transition history; README listed six of seven CLI commands and one of two distribution paths; the protocol specification said Python does not read the join corpus three lines after telling the reader to run a script that does.
The rename was told at full length in four documents, about a thousand words between them, because no document owned "what changed and what it asks of you". CHANGELOG.md owns it now. The README and the release guide keep the paragraph their own reader needs and link for the rest; the participant guide keeps its own, because a participant should not have to open a changelog. component-boundaries.md is retired. Every contract in it restated a row of the system design's module table, its invariants restated that document's security and privacy invariants, and its ASCII dependency sketch was a worse version of the mermaid diagram three sections earlier. Nothing was lost: the two claims worth checking — that a collector cannot see storage or the runtime, and that reclaiming runs between 80% and 60% of quota — were already stated there, in more detail. The system design listed six of the seven researcher-tools commands. Same omission the README had, in a document the earlier correctness pass did not cover: personalize was missing.
…nces The upload receipt contract was told in full in nine documents; it is now in the protocol specification, with a sentence of consequence and a link everywhere else. The same treatment for the HPKE and PTCEXP01 framing, the mechanism under signer trust, the demonstration-key warning, and the per-collector negative field lists: each has one owner, and the documents that used to repeat it keep only what their own reader needs. The caveat is not the mechanism, and the caveats all stay. "A signature proves the file is unchanged since it was signed, not who wrote it" appears in five documents on purpose, because five different readers need it where they are standing. What moved is the explanation of how the key travels in the signed bytes and how the fingerprint is derived. Sentences that were both long and clause-dense fall from 96 to 38, and the longest in the set from 78 words to 57. Nothing was shortened by deleting content: the count of sentences rises, because the fix for a 78-word sentence is three sentences rather than a shorter one. Net words barely move. The correctness pass before this added a section the participant guide was missing and three descriptions of the daily reminder, and splitting a sentence costs a few words. Deduplication paid for both.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eight commits, in the order they were made.
App
Report when a pause started and how long it has lasted. A pause is the one state a participant can leave a study in by accident: nothing on the phone changes and no notification is showing. The status line gains a second row —
Paused since Aug 7, 3:16 AM, 0m ago— rendered with Android'sDateUtils, so it follows the participant's locale and their 12/24-hour setting. The date is always shown, because a line reading "3:16" is indistinguishable from one three days old, which is the case it exists to catch.One reminder a day, running or paused. A low-importance notification, replacing yesterday's rather than stacking. It names the application and the state — never the study, never a count — because a lock screen is readable by whoever is holding the phone. Scheduled when collection starts, re-established on session init, deliberately not cancelled on pause, retired when the study ends. Starting or stopping collection retracts a standing one, so the shade never keeps asserting "Still collecting" after collection stopped.
Both branches were verified on an emulator against the real notification record.
Move to
cool.jacoblin.particeps. The previous namespace carried a personal handle the product does not use elsewhere, and the release certificate's subject still named the pre-rename product. Neither is correctable later. Doing both now cost nothing: every tag published so far is a pre-1.0 release candidate and Developer Verification had not been registered. The signing key was rotated with the corrected subjectCN=Particeps, O=Particeps, L=Taipei, ST=Taiwan, C=TW.tools/retired_identity_audit.pynow knows two dead namespaces and refuses a reference to either, with a repository-wide pattern for the second — verified by injection into a Python tool, a workflow, and TypeScript.Documentation
An audit compared all 18 documents against the code.
Correctness first. The participant guide documented one way to enrol while the application ships two: the researcher guide tells researchers to recruit with a
particeps://join/v1link or QR code, and the participant scanning it held the one document that never mentioned it. Its four result codes are now in the troubleshooting table, one of them marked as not a fault. A limitation nobody had written down is now stated: the join URI carriessigner_fingerprint, so a participant who arrives by link is shown a fingerprint that came from that same link, and comparing the two shows only that the link agrees with itself.The collector implementation guide was wrong in six ways that would each mislead someone writing a collector —
EmitResulthas four members and it documented three,AccessKindhas ten and it listed seven, two lifecycle signatures had the wrong return types, a module was said to hold two files and holds five, and two members were named that do not exist. Verified against the source, member by member.The daily reminder is now in the threat model as a disclosure surface a bystander reads without decrypting anything, in the system design, and in the researcher guide — with its limits: best-effort timing, no configuration field to switch it off, no guarantee anyone was reminded.
Then structure.
docs/p0-p2-implementation-contract.mdis retired: a plan written in the future tense about work that shipped three releases ago.docs/component-boundaries.mdis retired: every contract in it restated the system design's module table.CHANGELOG.mdis new, and owns the release history that four documents were each telling in full.Then duplication. The upload receipt contract was told in nine documents and is now in the protocol specification with a sentence and a link elsewhere. Same for the HPKE framing, the mechanism under signer trust, the demonstration-key warning, and the negative field lists. The caveats all stay — "a signature proves the file is unchanged since it was signed, not who wrote it" appears in five documents on purpose.
Sentences that were both long and clause-dense fall from 96 to 38; the longest in the set from 78 words to 57.
Net words barely move, and that is honest: the correctness pass added a section the participant guide was missing and three descriptions of the reminder. Deduplication paid for them.